package com.meeruu.sg.account.core.domain.facade.impl;

import com.alibaba.fastjson.JSON;
import com.meeruu.common.model.error.BaseCode;
import com.meeruu.common.model.result.BaseRpcResult;
import com.meeruu.common.utils.LoggerUtil;
import com.meeruu.common.utils.mapper.OrikaMapperUtils;
import com.meeruu.sg.account.core.api.constant.AccountGroup;
import com.meeruu.sg.account.core.api.constant.AccountType;
import com.meeruu.sg.account.core.api.dto.request.fund.*;
import com.meeruu.sg.account.core.api.exception.AccountException;
import com.meeruu.sg.account.core.api.exception.ErrorCode;
import com.meeruu.sg.account.core.api.service.IAccountFundService;
import com.meeruu.sg.account.core.domain.biz.FundsFrozenBiz;
import com.meeruu.sg.account.core.domain.biz.FundsTransferBiz;
import com.meeruu.sg.account.core.domain.component.lock.BizLock;
import com.meeruu.sg.account.core.domain.component.lock.LockTemplate;
import com.meeruu.sg.account.core.domain.model.AccountFrozenFlowDO;
import com.meeruu.sg.account.core.domain.util.AssertUtils;
import com.meeruu.sg.account.core.domain.vo.UnFrozenDetailRequest;
import com.meeruu.sg.account.core.domain.vo.UnFrozenToOtherDetailRequest;
import com.meeruu.summerframework.cloud.anno.MRSCProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;

/**
 * @Auther: liwu
 * @Date: 2019/5/15 14:31
 * @Description:
 */
@Slf4j
@MRSCProvider(serviceInterface = IAccountFundService.class)
@Component
public class AccountFundServiceImpl implements IAccountFundService {


    @Autowired
    private LockTemplate lockTemplate;

    @Autowired
    private FundsTransferBiz fundsTransferBiz;

    @Autowired
    private FundsFrozenBiz fundsFrozenBiz;

    @Override
    public BaseRpcResult<Void> charge(ChargeRequest request) throws AccountException {
        LoggerUtil.info(log, "账户充值请求开始,param chargeRequest: %s", JSON.toJSONString(request));

        try {

            this.checkChargeParam(request);

            //发起提现
            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(request.getAccountId())
                            .method("charge")
                            .expire(15)
                            .build()
                    , () -> {

                        fundsTransferBiz.charge(request);
                        return new BaseRpcResult<>();
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户充值异常,param chargeRequest: %s", f, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(f.getCode(), "账户充值出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户充值异常,param chargeRequest: %s", ex, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }

    }


    @Override
    public BaseRpcResult<Void> transfer(FundTransRequest fundTransRequest) throws AccountException {
        LoggerUtil.info(log, "账户转账请求开始,param fundTransRequest: %s", JSON.toJSONString(fundTransRequest));

        try {

            this.checkTransferParam(fundTransRequest);

            //发起提现
            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(fundTransRequest.getOutBizNo())
                            .method("applyTransfer")
                            .expire(15)
                            .build()
                    , () -> {

                        fundsTransferBiz.applyTransfer(fundTransRequest);
                        return new BaseRpcResult<>();
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户转账异常,param fundTransRequest: %s", f, JSON.toJSONString(fundTransRequest));
            return new BaseRpcResult<Void>().error(f.getCode(), "转账出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户转账异常,param fundTransRequest: %s", ex, JSON.toJSONString(fundTransRequest));
            return new BaseRpcResult<Void>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }

    }

    @Override
    public BaseRpcResult<String> frozen(FrozenRequest request) {
        LoggerUtil.info(log, "账户资金冻结请求开始,param request: %s", JSON.toJSONString(request));

        try {

            this.checkFrozenParam(request);

            //发起提现
            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(request.getOutBizNo())
                            .method("frozen")
                            .expire(15)
                            .build()
                    , () -> {

                        String frozenFlowId = fundsFrozenBiz.frozen(request);
                        return new BaseRpcResult<>(frozenFlowId);
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户资金冻结异常,param request: %s", f, JSON.toJSONString(request));
            return new BaseRpcResult<String>().error(f.getCode(), "账户资金冻结出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户资金冻结异常,param request: %s", ex, JSON.toJSONString(request));
            return new BaseRpcResult<String>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }
    }


    @Override
    public BaseRpcResult<Void> unFrozen(UnFrozenRequest request) {
        LoggerUtil.info(log, "账户资金逆向解冻请求开始,param request: %s", JSON.toJSONString(request));

        try {

            this.checkUnFrozenParam(request);

            //发起提现
            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(request.getFrozenId())
                            .method("unFrozen")
                            .expire(15)
                            .build()
                    , () -> {

                        // 获取原冻结记录
                        AccountFrozenFlowDO frozenFlow = fundsFrozenBiz.getFrozenFlow(request.getFrozenId());
                        // 入参转换,方便记录请求流水
                        UnFrozenDetailRequest detailRequest = this.transRequest(frozenFlow, request);

                        fundsFrozenBiz.unFrozen(detailRequest);
                        return new BaseRpcResult<>();
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户资金逆向解冻异常,param request: %s", f, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(f.getCode(), "账户资金逆向解冻出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户资金逆向解冻异常,param request: %s", ex, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }
    }


    @Override
    public BaseRpcResult<Void> unFrozenToOther(UnFrozenToOtherRequest request) {
        LoggerUtil.info(log, "账户资金正向解冻请求开始,param request: %s", JSON.toJSONString(request));

        try {

            this.checkUnFrozenToOtherParam(request);

            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(request.getFrozenId())
                            .method("unFrozenToOther")
                            .expire(15)
                            .build()
                    , () -> {

                        // 获取原冻结流水
                        AccountFrozenFlowDO frozenFlow = fundsFrozenBiz.getFrozenFlow(request.getFrozenId());
                        // 入参转换,方便记录请求流水
                        UnFrozenToOtherDetailRequest detailRequest = transRequest(frozenFlow, request);

                        fundsFrozenBiz.unFrozenToOther(detailRequest);
                        return new BaseRpcResult<>();
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户资金正向解冻异常,param request: %s", f, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(f.getCode(), "账户资金正向解冻出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户资金正向解冻异常,param request: %s", ex, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }
    }

    @Override
    public BaseRpcResult<Void> delayInTransfer(FundDelayInTransRequest request) {
        LoggerUtil.info(log, "账户转账请求开始,param request: %s", JSON.toJSONString(request));

        try {

            this.checkTransferParam(OrikaMapperUtils.map(request, FundTransRequest.class));

            return lockTemplate.execute(BizLock.builder()
                            .bizType("Fund")
                            .bizId(request.getOutBizNo())
                            .method("delayInTransfer")
                            .expire(15)
                            .build()
                    , () -> {

                        fundsTransferBiz.delayInTransfer(request);
                        return new BaseRpcResult<>();
                    });
        } catch (AccountException f) {
            LoggerUtil.error(log, "账户转账:入账户延时到账异常,param request: %s", f, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(f.getCode(), "转账出现异常:" + f.getMessage());
        } catch (Throwable ex) {
            LoggerUtil.error(log, "账户转账:入账户延时到账异常,param request: %s", ex, JSON.toJSONString(request));
            return new BaseRpcResult<Void>().error(BaseCode.SYSTEM_FAILD.getCode(), ex.getMessage());
        }
    }


    /**
     * 充值参数校验
     */
    private void checkChargeParam(ChargeRequest request) {
        AssertUtils.isTrue(null == request, ErrorCode.PARAM_ERROR.getCode(), "充值参数不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOutBizNo()), ErrorCode.REQUEST_NO_EMPTY.getCode(), ErrorCode.REQUEST_NO_EMPTY.getMessage());
        AssertUtils.isTrue(StringUtils.isEmpty(request.getAccountId()), ErrorCode.PARAM_ERROR.getCode(), "充值 accountId不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getAccountType()), ErrorCode.PARAM_ERROR.getCode(), "充值 accountType不能为空");

        AssertUtils.isTrue(StringUtils.isEmpty(request.getChargeAmount()), ErrorCode.PARAM_ERROR.getCode(), "充值金额不能为空");
        AssertUtils.isTrue(request.getChargeAmount().compareTo(BigDecimal.ZERO) < 0, ErrorCode.PARAM_ERROR.getCode(), "充值金额不能小于0");
        AssertUtils.isTrue(request.getAccountGroup() == null, ErrorCode.PARAM_ERROR.getCode(), "账户 accountGroup不能为空");
        AssertUtils.isTrue(request.getApplyDate() == null, ErrorCode.PARAM_ERROR.getCode(), "申请时间不能为空");

    }


    /**
     * 转账参数校验
     */
    private void checkTransferParam(FundTransRequest request) {
        AssertUtils.isTrue(null == request, ErrorCode.PARAM_ERROR.getCode(), "转账信息不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOutBizNo()), ErrorCode.REQUEST_NO_EMPTY.getCode(), ErrorCode.REQUEST_NO_EMPTY.getMessage());
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOriginAccountId()), ErrorCode.PARAM_ERROR.getCode(), "转账OriginAccountId不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOriginAccountGroup()), ErrorCode.PARAM_ERROR.getCode(), "转账OriginAccountGroup不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOriginAccountType()), ErrorCode.PARAM_ERROR.getCode(), "转账OriginAccountType不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getTargetAccountId()), ErrorCode.PARAM_ERROR.getCode(), "转账TargetAccountId不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getTargetAccountGroup()), ErrorCode.PARAM_ERROR.getCode(), "转账TargetAccountGroup不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getTargetAccountType()), ErrorCode.PARAM_ERROR.getCode(), "转账TargetAccountType不能为空");
        AssertUtils.isTrue(null == request.getApplyDate(), ErrorCode.PARAM_ERROR.getCode(), "转账 applyDate 不能为空");

        AssertUtils.isTrue(StringUtils.isEmpty(request.getTransAmount()), ErrorCode.PARAM_ERROR.getCode(), "转账金额不能为空");
        AssertUtils.isTrue(request.getTransAmount().compareTo(BigDecimal.ZERO) < 0, ErrorCode.PARAM_ERROR.getCode(), "转账金额不能小于0");

    }

    /**
     * 冻结参数校验
     */
    private void checkFrozenParam(FrozenRequest request) {

        AssertUtils.isTrue(null == request, ErrorCode.PARAM_ERROR.getCode(), "冻结信息不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOutBizNo()), ErrorCode.REQUEST_NO_EMPTY.getCode(), ErrorCode.REQUEST_NO_EMPTY.getMessage());
        AssertUtils.isTrue(StringUtils.isEmpty(request.getAccountId()), ErrorCode.PARAM_ERROR.getCode(), "冻结 accountId 不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getAccountGroup()), ErrorCode.PARAM_ERROR.getCode(), "冻结账户类型不能为空");
        AssertUtils.isTrue(null == request.getApplyDate(), ErrorCode.PARAM_ERROR.getCode(), "冻结 applyDate 不能为空");

        AssertUtils.isTrue(StringUtils.isEmpty(request.getAmount()), ErrorCode.PARAM_ERROR.getCode(), "冻结金额不能为空");
        AssertUtils.isTrue(request.getAmount().compareTo(BigDecimal.ZERO) < 0, ErrorCode.PARAM_ERROR.getCode(), "冻结金额不能小于0");

    }

    /**
     * 逆向解冻参数校验
     */
    private void checkUnFrozenParam(UnFrozenRequest request) {
        AssertUtils.isTrue(null == request, ErrorCode.PARAM_ERROR.getCode(), "逆向解冻信息不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOutBizNo()), ErrorCode.REQUEST_NO_EMPTY.getCode(), ErrorCode.REQUEST_NO_EMPTY.getMessage());
        AssertUtils.isTrue(StringUtils.isEmpty(request.getFrozenId()), ErrorCode.PARAM_ERROR.getCode(), "逆向解冻 frozenId 不能为空");
        AssertUtils.isTrue(null == request.getApplyDate(), ErrorCode.PARAM_ERROR.getCode(), "逆向解冻 applyDate 不能为空");
    }


    /**
     * 正向解冻参数校验
     */
    private void checkUnFrozenToOtherParam(UnFrozenToOtherRequest request) {
        AssertUtils.isTrue(null == request, ErrorCode.PARAM_ERROR.getCode(), "正向解冻信息不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getOutBizNo()), ErrorCode.REQUEST_NO_EMPTY.getCode(), ErrorCode.REQUEST_NO_EMPTY.getMessage());
        AssertUtils.isTrue(StringUtils.isEmpty(request.getFrozenId()), ErrorCode.PARAM_ERROR.getCode(), "正向解冻 frozenId 不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getTargetAccountId()), ErrorCode.PARAM_ERROR.getCode(), "正向解冻 targetAccountId 不能为空");
        AssertUtils.isTrue(StringUtils.isEmpty(request.getTargetAccountGroup()), ErrorCode.PARAM_ERROR.getCode(), "正向解冻 targetAccountGroup 不能为空");
        AssertUtils.isTrue(null == request.getApplyDate(), ErrorCode.PARAM_ERROR.getCode(), "正向解冻 applyDate 不能为空");
    }


    /**
     * request转换，用于请求流水AOP
     *
     * @param frozenFlow 原冻结记录
     * @param request    逆向解冻入参
     * @return 逆向解冻入参详细参数
     */
    private UnFrozenDetailRequest transRequest(AccountFrozenFlowDO frozenFlow, UnFrozenRequest request) {
        UnFrozenDetailRequest detailRequest = OrikaMapperUtils.map(request, UnFrozenDetailRequest.class);
        detailRequest.setAccountId(frozenFlow.getAccountId());
        detailRequest.setAccountType(AccountType.getTypeByCode(frozenFlow.getAccountType()));
        detailRequest.setAccountGroup(AccountGroup.getByCode(frozenFlow.getAccountGroup()));
        // 先取传入金额，传入金额为空时取冻结金额
        BigDecimal amount = request.getAmount() == null ? frozenFlow.getFlowFrozen() : request.getAmount();
        detailRequest.setAmount(amount);
        return detailRequest;
    }

    /**
     * request转换，用于请求流水AOP
     *
     * @param frozenFlow 原冻结记录
     * @param request    正向解冻入参
     * @return 正向解冻入参详细参数
     */
    private UnFrozenToOtherDetailRequest transRequest(AccountFrozenFlowDO frozenFlow, UnFrozenToOtherRequest request) {
        UnFrozenToOtherDetailRequest detailRequest = OrikaMapperUtils.map(request, UnFrozenToOtherDetailRequest.class);
        detailRequest.setAccountId(frozenFlow.getAccountId());
        detailRequest.setAccountType(AccountType.getTypeByCode(frozenFlow.getAccountType()));
        detailRequest.setAccountGroup(AccountGroup.getByCode(frozenFlow.getAccountGroup()));
        // 先取传入金额，传入金额为空时取冻结金额
        BigDecimal amount = request.getAmount() == null ? frozenFlow.getFlowFrozen() : request.getAmount();
        detailRequest.setAmount(amount);
        return detailRequest;
    }

}
